using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Text;
using SubSonic;
using StructureMap;

using Impact.Infrastructure.Configuration;
using Impact.Infrastructure.Exceptions;
using Impact.Infrastructure.Logging;
using Impact.Model.Objects;
using Impact.Views;
using Impact.Model.Service;
using Impact.Infrastructure;
using Impact.Controllers.Properties;
using System.Text.RegularExpressions;
using System.Web.UI;

namespace Impact.Controllers
{
    /// <summary>
    /// This class is responsible for presenting the data requested by the user through the view.  
    /// </summary>
    public sealed class ContentPresenter
    {
        private IContentHandler handler;
        private static FrontController controller;
        private IContentView view;

        /// <summary>
        /// default constructor
        /// </summary>
        /// <param name="handler">the handler that created this controller</param>
        /// <param name="page">the page that is being used to render the view</param>
        public ContentPresenter( IContentHandler handler, IContentView page )
        {
            this.handler = handler;

            if ( controller == null )
            {
                controller = new FrontController();
                controller.Add( AllCategories_Query, AllCategories_Action );
                controller.Add( SingleArticle_Query, SingleArticle_Action );
                controller.Add( DisplayTag_Query, DisplayTag_Action );
                controller.Add( Category_Query, Category_Action );
                controller.Add( SearchResults_Query, SearchResults_Action );
                controller.Add( Preview_Query, Preview_Action );

                // if all the other conditions pass through, we will attempt to handle
                // it as a page.  thus, the 'return true' as the query.
                controller.Add( delegate( Uri url ) { return true; }, Page_Action );
            }


            page.PreInit += new EventHandler( PageInit );
            page.PreLoad += new EventHandler( PageLoad );
            
        }

        /// <summary>
        /// this method uses the front controller to decide what view the
        /// user is requesting.  this is done by parsing the URL that made the request,
        /// and then passing the results to the page to render the view control
        /// </summary>
        /// <param name="sender">the page that will render the view.  must implement IContentView.</param>
        /// <param name="e"></param>
        private void PageInit( object sender, EventArgs e )
        {
            Check.Require( sender, "Sender not specified" );
            try
            {

                ActionResult result = controller.Process( handler.RequestUrl );

                if ( result.Continue )
                {

                    // send results to view
                    this.view = sender as IContentView;

                    if ( !string.IsNullOrEmpty( result.ErrorMessage ) )
                    {
                        view.ShowErrorMessage( result.ErrorMessage );
                    }
                    else if ( !string.IsNullOrEmpty( result.ControlName ) )
                    {
                        ExceptionManager.Handle( "Request served: " + result.ControlName, System.Diagnostics.TraceEventType.Information );
                        view.ContentInit(result.ControlName, result.DataSource);
                    }
                    else
                    {
                        view.ShowErrorMessage( string.Format( "Could not load view: {0}", handler.RequestUrl ) );
                    }

                }


            }
            catch ( Exception ex )
            {
                ExceptionManager.Handle( ex );
            }
        }

        private void PageLoad(object sender, EventArgs e)
        {
            if (view != null)
            {
                view.ContentRender();
            }
        }


        /// <summary>
        /// true if a page should be rendered
        /// </summary>
        public bool RenderPage
        {
            get { return renderPage; }
            set { renderPage = value; }
        }
        private bool renderPage = true;



        #region All Categories

        private bool AllCategories_Query( Uri url )
        {
            return Regex.IsMatch( url.PathAndQuery, string.Format( "/{0}/", Article.ArticleFolder ) ) &&
                Regex.IsMatch( url.PathAndQuery, UrlPatterns.Categories );
        }

        private ActionResult AllCategories_Action( Uri requestedUrl )
        {
            ActionResult result = new ActionResult();

            try
            {
                // show all categories
                result.ControlName = ControlPaths.DisplayCategories;
                // no datasource for this control
                result.MasterDataSource = ArticleController.GetCategoryTemplate();
            }
            catch ( Exception ex )
            {
                string message = string.Format( "Error occured while trying to load all categories: {0}", requestedUrl );
                ApplicationException appEx = new ApplicationException( message, ex );
                throw appEx;
            }

            return result;
        }

        #endregion

        #region Single Article

        private bool SingleArticle_Query( Uri url )
        {
            return Regex.IsMatch( url.PathAndQuery, string.Format( "/{0}/", Article.ArticleFolder ) ) &&
                Regex.IsMatch( url.PathAndQuery, UrlPatterns.Article );
        }

        private ActionResult SingleArticle_Action( Uri requestedUrl )
        {
            ActionResult result = new ActionResult();

            try
            {
                // show a single article

                result.ControlName = ControlPaths.DisplayArticle;

                Article article = Article.FetchByUrl( requestedUrl );
                if ( Article.IsValid( article ) )
                {
                    if ( !article.IsDeleted )
                    {
                        result.DataSource = article;
                        result.MasterDataSource = ArticleController.GetArticleTemplate();
                        IncrementViews( article );
                    }
                    else
                    {
                        result.ErrorMessage = "This article has been deleted and is no longer available.";
                    }
                }
                else
                {
                    result.ErrorMessage = string.Format( "Article not found: {0}", requestedUrl );
                }
            }
            catch ( Exception ex )
            {
                string message = string.Format( "Error occured while trying to load an article: {0}", requestedUrl );
                ApplicationException appEx = new ApplicationException( message, ex );
                throw appEx;
            }

            return result;

        }

        #endregion

        #region Display Tag

        private bool DisplayTag_Query( Uri url )
        {
            return Regex.IsMatch( url.PathAndQuery, string.Format( "/{0}/", Article.ArticleFolder ) ) &&
                Regex.IsMatch( url.PathAndQuery, UrlPatterns.Tag );
        }

        private ActionResult DisplayTag_Action( Uri requestedUrl )
        {
            ActionResult result = new ActionResult();

            try
            {
                // show articles with the given tag

                result.ControlName = ControlPaths.DisplayTag;

                // pull out the tag text
                Match match = Regex.Match( requestedUrl.PathAndQuery, UrlPatterns.AshxFilename );
                string tagText = match.Value.Replace( "/", string.Empty ).Replace( ".ashx", string.Empty );
                tagText = handler.DecodeUrl( tagText ).Trim();

                Tag tag = tagService.FetchByText( tagText );
                if( Tag.IsValid( tag ) )
                {
                    result.DataSource = tag;
                    result.MasterDataSource = ArticleController.GetTagTemplate();
                }
                else
                {
                    result.ErrorMessage = string.Format( "Tag not found: {0}", tagText );
                }
            }
            catch ( Exception ex )
            {
                string message = string.Format( "Error occured while trying to load articles for a tag: {0}", requestedUrl );
                ApplicationException appEx = new ApplicationException( message, ex );
                throw appEx;
            }

            return result;
        }

        #endregion

        #region Single Category

        private bool Category_Query( Uri url )
        {
            return Regex.IsMatch( url.PathAndQuery, string.Format( "/{0}/", Article.ArticleFolder ) );
        }

        private ActionResult Category_Action( Uri requestedUrl )
        {
            ActionResult result = new ActionResult();

            try
            {
                // show the given category
                result.ControlName = ControlPaths.DisplayCategory;

                Match match = Regex.Match( requestedUrl.PathAndQuery, UrlPatterns.AshxFilename );
                string categoryName = match.Value.Replace( "/", string.Empty ).Replace( ".ashx", string.Empty );
                ArticleCategory category = categoryService.FetchByKey( categoryName );
                if( ArticleCategory.IsValid( category ) )
                {
                    result.DataSource = category;
                    result.MasterDataSource = ArticleController.GetCategoryTemplate();
                }
                else
                {
                    result.ErrorMessage = string.Format( "Category not found: {0}", categoryName );
                }
            }
            catch ( Exception ex )
            {
                string message = string.Format( "Error occured while trying to load articles for a category: {0}", requestedUrl );
                ApplicationException appEx = new ApplicationException( message, ex );
                throw appEx;
            }

            return result;
        }

        #endregion

        #region Search Results

        private bool SearchResults_Query( Uri url )
        {
            return Regex.IsMatch( url.PathAndQuery, UrlPatterns.Search );
        }

        private ActionResult SearchResults_Action( Uri requestedUrl )
        {
            ActionResult result = new ActionResult();

            try
            {
                result.ControlName = ControlPaths.DisplaySearchResults;

                string terms = handler.QueryString["terms"];
                if( !string.IsNullOrEmpty( terms ) )
                {
                    result.DataSource = SearchController.Execute( terms );
                    result.MasterDataSource = SearchController.GetSearchTemplate();
                }
                else
                {
                    result.ErrorMessage = "No search terms provided.";
                }
            }
            catch ( Exception ex )
            {
                string message = string.Format( "Error occured while trying to execute search: {0}", requestedUrl );
                ApplicationException appEx = new ApplicationException( message, ex );
                throw appEx;
            }

            return result;
        }

        #endregion

        #region Preview

        private bool Preview_Query( Uri url )
        {
            return Regex.IsMatch( url.PathAndQuery, UrlPatterns.Preview );
        }

        private ActionResult Preview_Action( Uri requestedUrl )
        {
            ActionResult result = new ActionResult();

            try
            {
                int articleId = -1;
                string articleIdParam = handler.QueryString["articleId"];
                int pageId = -1;
                string pageIdParam = handler.QueryString["pageId"];

                if( !string.IsNullOrEmpty( articleIdParam )
                    && Int32.TryParse( articleIdParam, out articleId ) )
                {
                    Article article = articleService.FetchByID( articleId );
                    if( Article.IsValid( article ) )
                    {
                        result.ControlName = ControlPaths.DisplayArticle;
                        result.DataSource = article;
                        result.MasterDataSource = ArticleController.GetArticleTemplate();
                    }
                }
                else if( !string.IsNullOrEmpty( pageIdParam )
                         && Int32.TryParse( pageIdParam, out pageId ) )
                {
                    ImpactPage page = pageService.FetchByID( pageId );
                    if( ImpactPage.IsValid( page ) )
                    {
                        result.ControlName = ControlPaths.DisplayPage;
                        result.DataSource = page;
                        result.MasterDataSource = page.Template;
                    }
                }
                else
                {
                    result.ErrorMessage = "No valid parameters to preview.";
                }
            }
            catch (Exception ex)
            {
                string message = string.Format( "Error occured while trying to load preview: {0}", requestedUrl );
                ApplicationException appEx = new ApplicationException(message, ex);
                throw appEx;
            }


            return result;
        }

        #endregion

        #region Standard Page

        private ActionResult Page_Action( Uri requestedUrl )
        {
            ActionResult result = new ActionResult();

            try
            {
                result.ControlName = ControlPaths.DisplayPage;

                string pageKey = GetPageKey( handler.RequestUrl );
                ImpactPage page = pageService.FetchByKey( pageKey );

                if( ImpactPage.IsValid( page ) && page.Enabled )
                {
                    if( page.PageType.Equals( PageType.Hyperlink ) )
                    {
                        handler.Redirect( page.LinkTarget );
                        result.Continue = false;
                    }
                    else
                    {
                        result.DataSource = page;
                        result.MasterDataSource = page.Template;
                    }
                }
                else
                {
                    // try to load the original aspx page from disk
                    handler.RenderRequest();

                    this.RenderPage = false;
                    result.Continue = false;

                }
            }
            catch (Exception ex)
            {
                string message = string.Format( "Error occured while trying to load page: {0}", requestedUrl );
                ApplicationException appEx = new ApplicationException(message, ex);
                throw appEx;
            }

            return result;
        }

        #endregion
        
        #region lazy load service helpers
        private IArticleService articleService
        {
            get
            {
                if ( _articleService == null ) _articleService = ObjectFactory.GetInstance<IArticleService>();
                return _articleService;
            }
        }
        private IArticleService _articleService;

        private ICategoryService categoryService
        {
            get
            {
                if ( _categoryService == null ) _categoryService = ObjectFactory.GetInstance<ICategoryService>();
                return _categoryService;
            }
        }
        private ICategoryService _categoryService;

        private IPageService pageService
        {
            get
            {
                if ( _pageService == null ) _pageService = ObjectFactory.GetInstance<IPageService>();
                return _pageService;
            }
        }
        private IPageService _pageService;

        private ITagService tagService
        {
            get
            {
                if ( _tagService == null ) _tagService = ObjectFactory.GetInstance<ITagService>();
                return _tagService;
            }
        }
        private ITagService _tagService;
        #endregion


        /// <summary>
        /// returns the pageKey portion of a url, if it exists
        /// </summary>
        public static string GetPageKey( Uri requestedUrl )
        {
            string pageKey = null;
            string urlString = requestedUrl.LocalPath;
            string requestedFile = urlString.Substring( urlString.LastIndexOf( '/' ) + 1 );

            // ignore requests for any file that starts with '!', indicating an Impact page
            if ( !requestedFile.StartsWith( "!" ) )
            {
                pageKey = requestedFile.Substring( 0, requestedFile.IndexOf( '.' ) );
            }
            if ( !string.IsNullOrEmpty( pageKey ) &&
                pageKey.Equals( "default", StringComparison.CurrentCultureIgnoreCase ) )
            {
                ImpactSettings settings = ObjectFactory.GetInstance<IConfigProvider>().Settings;
                pageKey = settings.DefaultPage;
            }
            return pageKey;
        }

        /// <summary>
        /// Create a formatted URL for the given object
        /// </summary>
        public static string FormatUrl( ArticleTag tag )
        {


            return string.Format( "~/{0}/tags/{1}.ashx", Article.ArticleFolder, tag.Text );
        }

        /// <summary>
        /// Create a formatted URL for the given object
        /// </summary>
        public static string FormatUrl( Tag tag )
        {
            return string.Format( "~/{0}/tags/{1}.ashx", Article.ArticleFolder, tag.Text );
        }

        /// <summary>
        /// Create a formatted URL for the given object
        /// </summary>
        public static string FormatUrl( ArticleCategory category )
        {
            return string.Format( "~/{0}/{1}.ashx", Article.ArticleFolder, category.NameKey );
        }

        /// <summary>
        /// Create a formatted URL for the given object
        /// </summary>
        public static string FormatUrl( ArticleComment comment )
        {
            IArticleService articleService = ObjectFactory.GetInstance<IArticleService>();
            Article article = articleService.FetchByID( comment.ArticleId );
            return string.Format( "{0}#comment{1}", FormatUrl( article ), comment.CommentId );
        }

        /// <summary>
        /// Create a formatted URL for the given object
        /// </summary>
        public static string FormatUrl( Article article )
        {
            return string.Format( "~/{0}/{1}/{2}/{3}.ashx", Article.ArticleFolder, article.ArticleId,
                article.Category.NameKey, Utility.StripNonAlphaNumeric( article.Title ) );
        }

        /// <summary>
        /// Create a formatted URL for the given object
        /// </summary>
        public static string FormatUrl( ImpactPage page )
        {
            List<string> path = GetPath( page );
            return string.Format( "~/{0}.ashx", string.Join( "/", path.ToArray() ) );
        }

        /// <summary>
        /// create a formatted URL for the given object
        /// </summary>
        public static string FormatUrl( Profile profile )
        {
            return profile.Website;
        }

        /// <summary>
        /// recursively build the path based upon parent pages' keys
        /// </summary>
        /// <param name="page"></param>
        /// <returns></returns>
        private static List<string> GetPath( ImpactPage page )
        {
            List<string> output = null;
            if ( page.ParentPageId > 0 )
            {
                ImpactPage parent = new ImpactPage( page.ParentPageId );
                output = GetPath( parent );
            }
            else
            {
                output = new List<string>();
            }
            output.Add( page.PageKey );

            return output;
        }


        /// <summary>
        /// maintains a list of users that have viewed each article, so when the
        /// same user views an article multiple times, the view count is only incremented
        /// once.  right now this is stored in memory, so it will lose the count when 
        /// the app gets restarted.
        /// TODO: maybe this can be persisted somehow?  do we care if it's only in memory?
        /// </summary>
        /// <param name="article">the article to check for and increment the view count</param>
        private void IncrementViews( Article article )
        {
            if ( articleUserAgents == null )
            {
                articleUserAgents = new Dictionary<int, List<string>>();
            }

            bool found = false;
            if ( articleUserAgents.ContainsKey( article.ArticleId ) )
            {
                List<string> users = articleUserAgents[article.ArticleId];
                if ( users.Contains( handler.UserHostAddress ) )
                {
                    found = true;
                }
                else
                {
                    users.Add( handler.UserHostAddress );
                }
            }
            else
            {
                articleUserAgents.Add( article.ArticleId,
                    new List<string>( new string[] { handler.UserHostAddress } ) );
            }

            if ( !found )
            {
                article.UserViews += 1;

                IArticleService service = ObjectFactory.GetInstance<IArticleService>();
                service.Save( article );
            }

        }
        private static Dictionary<int, List<string>> articleUserAgents;






    }


}

